﻿using System;

using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Drawing;
using Silvermoon.OpenGL.Native;
using System.Drawing.Imaging;

namespace Silvermoon.OpenGL
{
    public class Device : IDisposable
    {
        #region ctor/dtor

        private Device()
            : base()
        {
        }

        public Device(System.Windows.Forms.Control window)
            : this()
        {
            this.window = window;
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledException);
            Initialize(window);
            window.Resize += new EventHandler(OnControlResize);
            IsSuspended = false;

        }

        void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            Cleanup();
        }

        public void Dispose()
        {
            Cleanup();
            AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
            GC.SuppressFinalize(this);
        }

        ~Device()
        {
            Dispose();
        }


        #endregion
        #region Fields

        private IntPtr eglDisplay;
        private IntPtr eglContext;
        private IntPtr eglSurface;
        private Rectangle viewPort;
        int config;
        private System.Windows.Forms.Control window;

        #endregion
        #region Properties

        public Rectangle ViewPort
        {
            get { return viewPort; }
            set
            {
                viewPort = value;
                gl.Viewport(value.Left, value.Top, value.Width, value.Height);
            }
        }

        #endregion
        #region private Methods

        private void Initialize(System.Windows.Forms.Control window)
        {
            int major, minor;

            //display = egl.GetDisplay(new EGLNativeDisplayType(window));
            eglDisplay = egl.GetDisplay(NativeDisplay.Default);
            egl.Initialize(eglDisplay, out major, out minor);
            eglSurface = CreateWindowSurface(window);
            eglContext = egl.CreateContext(eglDisplay, config, egl.NO_SURFACE, null);

            egl.MakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);

            ViewPort = window.ClientRectangle;

            egl.SwapInterval(eglDisplay, 1);
            //egl.SwapInterval(Display, 0);

            QueryVersion();
        }

        [Conditional("DEBUG")]
        private void QueryVersion()
        {
            string s = gl.GetString(gl.GL_VERSION);
            Trace.WriteLine(s);

            s = gl.GetString(gl.GL_VENDOR);
            Trace.WriteLine(s);

            s = gl.GetString(gl.GL_RENDERER);
            Trace.WriteLine(s);
            s = egl.QueryString(eglDisplay, egl.EGL_VERSION);
            Trace.WriteLine(s);


            s = egl.QueryString(eglDisplay, egl.EGL_VENDOR);
            Trace.WriteLine(s);
        }

        void OnControlResize(object sender, EventArgs e)
        {
            if (!IsSuspended) ViewPort = window.ClientRectangle;
        }

        public bool IsSuspended { get; private set; }

        public void Suspend()
        {
            if (!IsSuspended)
            {
                IsSuspended = true;
                //egl.MakeCurrent(eglDisplay, egl.NO_SURFACE, egl.NO_SURFACE, eglContext);
                egl.MakeCurrent(eglDisplay, egl.NO_SURFACE, egl.NO_SURFACE, egl.NO_CONTEXT);
                egl.DestroySurface(eglDisplay, eglSurface);

            }
        }

        public void Resume()
        {
            if (IsSuspended)
            {
                eglSurface = egl.CreateWindowSurface(eglDisplay, config, window.Handle, null);
                egl.MakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
                IsSuspended = false;
            }
        }


        private void Cleanup()
        {
            if (window!=null) window.Resize -= OnControlResize;
            window = null;
            Texture.Cleanup();
            if (eglDisplay != IntPtr.Zero)
            {
                egl.MakeCurrent(eglDisplay, egl.NO_SURFACE, egl.NO_SURFACE, egl.NO_CONTEXT);

                egl.DestroySurface(eglDisplay, eglSurface);
                egl.DestroyContext(eglDisplay, eglContext);
                egl.Terminate(eglDisplay);


            }
        }

        #endregion
        #region Methods

        public IntPtr CreateWindowSurface(System.Windows.Forms.Control window)
        {
            int[] configs = new int[10];

            int[] attribList = new int[] 
            {         
                egl.EGL_RED_SIZE, 5, 
                egl.EGL_GREEN_SIZE, 6, 
                egl.EGL_BLUE_SIZE, 5, 
                egl.EGL_ALPHA_SIZE,0,  
                egl.EGL_SURFACE_TYPE, egl.EGL_WINDOW_BIT,


                egl.EGL_DEPTH_SIZE, 0 , 
                egl.EGL_STENCIL_SIZE, 0,            
                egl.EGL_NONE, egl.EGL_NONE 
            };

            int numConfig;

            if (!egl.ChooseConfig(eglDisplay, attribList, configs, configs.Length, out numConfig) || numConfig < 1)
                throw new NotSupportedException("Unable to choose config.");

            config = configs[0];


            IntPtr eglSurface = egl.CreateWindowSurface(eglDisplay, config, window.Handle, null);
            if (eglSurface == IntPtr.Zero) throw new NotSupportedException("Could not create surface.");
            return eglSurface;
        }

        public void Present()
        {
            // note that eglFinish is performed within eglSwapBuffers.
            egl.SwapBuffers(eglDisplay, eglSurface);
        }

        public void ClearColor(float red, float green, float blue, float alpha)
        {
            gl.ClearColor(red, green, blue, alpha);
        }

        public void ClearColor(System.Drawing.Color color)
        {
            float div = 255F;
            gl.ClearColor((float)color.R / div, (float)color.G / div, (float)color.B / div, (float)color.A / div);
        }

        public void Perspective(float fovy, float aspect, float zNear, float zFar)
        {
            if (zNear <= 0F) throw new ArgumentOutOfRangeException("zNear must be positive.");
            if (zFar <= 0F) throw new ArgumentOutOfRangeException("zFar must be positive.");
            float xmin, xmax, ymin, ymax;

            ymax = zNear * (float)Math.Tan(fovy * Math.PI / 360.0F);
            ymin = -ymax;
            xmin = ymin * aspect;
            xmax = ymax * aspect;

            gl.Frustumf(xmin, xmax, ymax, ymin, zNear, zFar);
        }

        public void Orthof(float left, float right, float bottom, float top, float znear, float zfar)
        {
            gl.Orthof(left, right, bottom, top, znear, zfar);
        }

        #endregion

    }

}
